﻿using DBUtil.Attributes;
using DotNetCommon.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;

namespace DBUtil;

public partial class DBAccess
{
    #region EntityInfo
    public EntityInfo GetEntityInfo(Type type) => GetEntityInfoInternal(type, true);
    public EntityInfo GetEntityInfo<T>() where T : class, new() => GetEntityInfoInternal(typeof(T), true);

    private static readonly ConcurrentDictionary<string, Dictionary<Type, EntityInfo>> _caches = [];
    internal EntityInfo GetEntityInfoInternal<T>(bool needClone = false) => GetEntityInfoInternal(typeof(T), needClone);
    internal EntityInfo GetEntityInfoInternal(Type type) => GetEntityInfoInternal(type, false);
    internal EntityInfo GetEntityInfoInternal(Type type, bool needClone = false)
    {
        var dic = _caches.GetOrAdd(DBConn, _ => []);
        if (!dic.TryGetValue(type, out var entityInfo))
        {
            lock (dic)
            {
                GenerateEntityInfo(type, dic);
                entityInfo = dic[type];
            }
        }
        if (needClone) entityInfo = entityInfo.CloneEntityInfo();
        return entityInfo;
    }

    private static Type[] _datetimeTypes = [typeof(DateTime), typeof(DateTimeOffset), typeof(DateOnly), typeof(TimeOnly), typeof(TimeSpan)];
    private void GenerateEntityInfo(Type type, Dictionary<Type, EntityInfo> dic)
    {
        var entityInfo = new EntityInfo();
        dic.Add(type, entityInfo);
        var attrs = type.GetCustomAttributes();
        //TableName SchemaName
        TableName objName = null;
        if (attrs.FirstOrDefault(i => i is TableAttribute) is TableAttribute tableAttr)
        {
            //存在 [Table] 特性
            //[Table("t_user",Schema ="dbo")]
            objName = ParseQuotedName(tableAttr.Name);
            if (tableAttr.Schema.IsNotNullOrWhiteSpace())
            {
                //以单独指定的 schema 名称为准
                objName.SetSchemaName(ParseQuotedName(tableAttr.Schema).Name);
            }
        }
        else
        {
            //没有 [Table] 特性
            objName = CreateTableName();
            //class PersonEntity<int>{} => "PersonEntity"
            objName.SetName(type.Name.Split('`')[0]);
        }
        entityInfo.TableName = objName;

        entityInfo.Type = type;
        entityInfo.TypeClassFullName = type.GetClassFullName();

        //properties
        var tmp = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        //只读的忽略
        var props = tmp.Where(i => i.CanWrite).ToList();
        //设定: 子类会覆盖父类的同名属性, 如下面的定义将会产生两个 Id 子类的排序在前面
        //class PersonEntity : BaseEntity<int> { public new int Id { get; set; } }
        //abstract class BaseEntity<TPrimaryKey> { public virtual TPrimaryKey Id { get; set; } }
        props = props.DistinctBy(i => i.Name).ToList();
        for (int i = 0, count = props.Count; i < count; i++)
        {
            var prop = props[i];
            var propAttrs = prop.GetCustomAttributes();
            var entityPropertyInfo = new EntityPropertyInfo();
            entityPropertyInfo.PropertyInfo = prop;
            entityPropertyInfo.Type = prop.PropertyType;
            entityPropertyInfo.DefaultValue = prop.PropertyType.GetDefault();
            entityPropertyInfo.TypeClassFullName = prop.PropertyType.GetClassFullName();
            entityPropertyInfo.IsNullAbleStruct = prop.PropertyType.IsNullable();
            //属性名称
            entityPropertyInfo.PropNamePure = prop.Name;
            entityPropertyInfo.PropNameQuoted = AddQuote(prop.Name);

            var tmpType = entityPropertyInfo.IsNullAbleStruct ? entityPropertyInfo.Type.GenericTypeArguments[0] : entityPropertyInfo.Type;
            entityPropertyInfo.TypeCode = tmpType.GetTypeCode();
            entityPropertyInfo.IsEnum = tmpType.IsEnum;
            entityPropertyInfo.IsFlagEnum = entityPropertyInfo.IsEnum && tmpType.GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0;

            entityPropertyInfo.IsDateTime = tmpType == typeof(DateTime) || tmpType == typeof(DateTimeOffset);
            entityPropertyInfo.IsDate = tmpType == typeof(DateOnly);
            entityPropertyInfo.IsTime = tmpType == typeof(TimeOnly);
            entityPropertyInfo.IsTimeSpan = tmpType == typeof(TimeSpan);

            entityInfo.Props.Add(entityPropertyInfo);

            var colAttr = propAttrs.FirstOrDefault(i => i is ColumnAttribute) as ColumnAttribute;
            var jsonMapAttr = propAttrs.FirstOrDefault(i => i is JsonMapAttribute) as JsonMapAttribute;
            var isNotMapped = propAttrs.Any(i => i is NotMappedAttribute);
            var isIgnoreWrite = propAttrs.Any(i => i is IgnoreWriteAttribute);
            var primaryAttr = propAttrs.FirstOrDefault(i => i is PrimaryKeyAttribute) as PrimaryKeyAttribute;
            var identityAttr = propAttrs.FirstOrDefault(i => i is IdentityAttribute) as IdentityAttribute;
            var parentIdAttr = propAttrs.FirstOrDefault(i => i is ParentIdAttribute) as ParentIdAttribute;
            var childrenAttr = propAttrs.FirstOrDefault(i => i is ChildrenAttribute) as ChildrenAttribute;

            #region 先处理 CanMapToTable
            if (IsSimple(tmpType))
            {
                if (jsonMapAttr == null)
                {
                    entityPropertyInfo.CanMapToTable = true;
                }
                else
                {
                    throw new Exception($"类型: {tmpType.GetClassFullName()} 属于基础类型, 禁止使用 [JsonMap]!");
                }
            }
            else
            {
                entityPropertyInfo.CanMapToTable = jsonMapAttr != null;
            }
            #endregion

            #region Ignore
            if (!entityPropertyInfo.CanMapToTable || isNotMapped)
            {
                entityPropertyInfo.IsIgnoreInsert = entityPropertyInfo.IsIgnoreUpdate = entityPropertyInfo.IsIgnoreSelect = true;
            }
            else
            {
                entityPropertyInfo.IsIgnoreUpdate = isIgnoreWrite || propAttrs.Any(i => i is IgnoreUpdateAttribute);
                entityPropertyInfo.IsIgnoreInsert = isIgnoreWrite || propAttrs.Any(i => i is IgnoreInsertAttribute);
                entityPropertyInfo.IsIgnoreSelect = propAttrs.Any(i => i is IgnoreSelectAttribute);
            }
            #endregion

            #region [Column]
            entityPropertyInfo.ColumnNamePure = ParseQuotedName(colAttr?.Name ?? prop.Name).Name;
            entityPropertyInfo.ColumnNameQuoted = AddQuote(entityPropertyInfo.ColumnNamePure);

            entityPropertyInfo.TypeName = colAttr?.TypeName;
            entityPropertyInfo.IsColumnNameEqualPropName = entityPropertyInfo.PropNameQuoted == entityPropertyInfo.ColumnNameQuoted;
            // [Column] 源码中默认就是-1
            entityPropertyInfo.Order = colAttr?.Order ?? -1;
            entityPropertyInfo.IsDbString = colAttr?.TypeName?.Contains("char");
            if (entityPropertyInfo.IsDbString != true) entityPropertyInfo.IsDbString = colAttr?.TypeName?.Contains("text");
            #endregion

            #region [JsonMap]     
            entityPropertyInfo.IsJsonMap = jsonMapAttr != null;
            if (!entityPropertyInfo.IsJsonMap)
            {
                //兼容其他同类特性
                entityPropertyInfo.IsJsonMap = propAttrs.Any(i => i.GetType().Name.Contains("JsonMap", StringComparison.OrdinalIgnoreCase));
            }
            #endregion

            #region 主键            
            if (entityPropertyInfo.CanMapToTable && primaryAttr != null)
            {
                entityPropertyInfo.IsPrimaryKey = true;
                entityPropertyInfo.PrimaryKeyStrategy = EnumKeyStrategy.Custom;
                if (identityAttr != null) entityPropertyInfo.PrimaryKeyStrategy = EnumKeyStrategy.Identity;
            }
            #endregion

            #region tree
            if (parentIdAttr != null) entityPropertyInfo.IsParentId = true;
            if (childrenAttr != null) entityPropertyInfo.IsChildren = true;
            #endregion

            #region SelectFunc & SelectValueFunc
            entityPropertyInfo.SelectFunc = (string alias) =>
            {
                var ret = alias.IsNotNullOrWhiteSpace() ? $"{alias}.{entityPropertyInfo.ColumnNameQuoted}" : entityPropertyInfo.ColumnNameQuoted;
                if (!entityPropertyInfo.IsColumnNameEqualPropName) ret += $" {entityPropertyInfo.PropNameQuoted}";
                return ret;
            };
            entityPropertyInfo.SelectValueFunc = alias =>
            {
                return alias.IsNotNullOrWhiteSpace() ? $"{alias}.{entityPropertyInfo.ColumnNameQuoted}" : $"{entityPropertyInfo.ColumnNameQuoted}";
            };
            #endregion
        }
        //当联合主键时 不能有自增主键
        var tmp2 = entityInfo.Props.Where(i => i.IsPrimaryKey).ToList();
        if (tmp2.Count > 1 && tmp2.Any(i => i.PrimaryKeyStrategy == EnumKeyStrategy.Identity))
            throw new Exception($"不能将自增列 {tmp2.FirstOrDefault(i => i.PrimaryKeyStrategy == EnumKeyStrategy.Identity).ColumnNameQuoted} 加入到联合主键({tmp2.Select(i => i.ColumnNameQuoted).ToStringSeparated(",")})!");
        if (tmp2.Count > 6) throw new Exception($"联合主键最多支持6列!");
        PostDealEntityInfo(entityInfo);
        //填充信息
    }

    protected virtual void PostDealEntityInfo(EntityInfo entityInfo) { }
    #endregion
}
